package cn.com.bluemoon.common.security;


import cn.com.bluemoon.daps.common.datascope.config.AuthWebConfig;
import cn.com.bluemoon.daps.common.domain.ResultBean;
import cn.com.bluemoon.daps.common.domain.UserInfoHolder;
import cn.com.bluemoon.daps.common.exception.DapException;
import cn.com.bluemoon.daps.common.exception.DapThrowException;
import cn.com.bluemoon.daps.common.exception.DescribeException;
import cn.com.bluemoon.daps.common.webhook.WebhookClient;
import cn.com.bluemoon.daps.domp.api.MailService;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 本类对系统异常进行aop拦截，可以自定义组装自己的异常结构
 * 注：
 * 1.{@link ControllerAop}的@Order(Integer.MAX_VALUE - 2) 确保比{@link UserPermissionSingleRowAop}的order后，保证认证通过了才进行接口调用
 * 2.确保{@link UserPermissionSingleRowAop}的order 在拦截器{@link AuthWebConfig#addInterceptors(InterceptorRegistry)}的后
 * 保证{@link UserInfoHolder#getUserInfo()}已存在数据
 * 3.确保拦截器{@see AuthWebConfig#addInterceptors(InterceptorRegistry)}最先执行，权限认证
 */
@Slf4j
@Aspect
@Component
@Order(Integer.MAX_VALUE - 2)
@ConditionalOnProperty(
        value = {"bm.dap.http.aop.enable"},
        matchIfMissing = true
)
public class ControllerAop {
    @Resource
    private BmDapConfig bmDapConfig;

    @Resource
    @Lazy
    private MailService mailService;
    /**
     * 传递接口请求入参
     */
    private String reqJsonStr;

    /**
     * 获取异常详细信息，知道出了什么错，错在哪个类的第几行 .
     *
     * @param ex
     * @return
     */
    private static String getThrowableDetail(Throwable ex) {
        try (StringWriter sw = new StringWriter();
             PrintWriter pw = new PrintWriter(sw, true)) {
            ex.printStackTrace(pw);
            pw.flush();
            sw.flush();
            return sw.toString();
        } catch (IOException e) {
            log.error("生成异常代码异常", e);
            return e.getMessage();
        }
    }

    /**
     * 加大了aop的切面域，若不需要，可以配置bm.dap.http.aop.enable=false进行关闭该aop
     */
    @Pointcut("execution(* cn.com.bluemoon..*.controller..*.*(..))")
    public void log() {
        // nothing
    }

    /**
     * 源方法有问题，{@link JSON#toJSONString(Object)} 并不是所有入参对象都是可序列化的
     * eg: ServletRequest。ServletResponse、MultipartFile
     * 进行toJSONString会报错，导致mvc传递失败
     * 修正为，若无法序列化就直接返回原值，不输出日志
     *
     * @param pjp
     * @return
     */
    @Around("log()")
    public Object handlerControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result;
        Object[] args = pjp.getArgs();
        try {
            if (args == null) {
                args = new Object[]{};
            }
            Object[] arguments = Arrays.stream(args)
                    .filter(i -> !(i instanceof ServletRequest || i instanceof ServletResponse || i instanceof MultipartFile)).toArray();
            String requestJsonStr = null;
            try {
                requestJsonStr = JSON.toJSONString(arguments);
            } catch (Exception e) {
                log.error("切面入参转为json异常，避免影响接口正常调用，内部报错，请排除！！", e);
            }
            log.info("args: {}", requestJsonStr);
            this.reqJsonStr = requestJsonStr;
            Object proceed = pjp.proceed();
            long endTime = System.currentTimeMillis();
            if (proceed instanceof ResultBean<?>) {
                ResultBean<?> resultBean = (ResultBean<?>) proceed;
                result = resultBean.setCost(Duration.of((endTime - startTime), ChronoUnit.MILLIS));
            } else {
                result = proceed;
            }
            log.info(pjp.getSignature() + "use time:" + (endTime - startTime) + "ms");
        } catch (DapThrowException e) {
            throw e;
        } catch (Exception e) {
            result = handlerException(pjp, e);
        }
        return result;
    }

    /**
     * 封装异常信息，注意区分已知异常（自己抛出的）和未知异常
     * 2020年8月11日10:15:37 增加针对业务层抛出的异常进行组装返回
     */
    private ResultBean<?> handlerException(ProceedingJoinPoint pjp, Throwable e) {
        ResultBean<?> result = ResultBean.FAIL;
        String msg = e.getMessage();
        boolean unknownException = true;
        // 已知异常
        if (e instanceof MethodArgumentNotValidException) {
            unknownException = false;
            MethodArgumentNotValidException validException = (MethodArgumentNotValidException) e;
            List<ObjectError> errors = validException.getBindingResult().getAllErrors();
            msg = errors.stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(";"));
        } else if (e instanceof DescribeException) {
            unknownException = false;
            msg = e.getLocalizedMessage();
        } else if (e instanceof ConstraintViolationException) {
            unknownException = false;
            ConstraintViolationException violationException = (ConstraintViolationException) e;
            msg = violationException.getLocalizedMessage();
        } else if (e instanceof DapException) {
            unknownException = false;
            DapException dapException = (DapException) e;
            msg = dapException.getMessage();
        }
        log.error(pjp.getSignature() + " error ", e);
        //未知的异常，应该格外注意，可以发送邮件通知等 发送到webhook
        if (unknownException && bmDapConfig.getException().isSendWebhook()) {
            String relateDevUserInfo = WebhookClient.buildDevUserInfo(pjp);
            String errorMsg = String.format("[%s]系统提示：%n相关开发人员：%s %n接口访问者：%s %n接口入参为：%s %n未知异常，详情如下：%n%s",
                    bmDapConfig.getAppName(), relateDevUserInfo, UserInfoHolder.getUserName(), reqJsonStr, getThrowableDetail(e));
            log.error("发送异常信息到webhook");
            WebhookClient.sendWechatWebhook(errorMsg);
        }
        List<String> sendMails = bmDapConfig.getException().getSendMails();
        if (unknownException && sendMails != null && !sendMails.isEmpty()) {
            String throwableDetail = getThrowableDetail(e);
            sendMails.stream().parallel().filter(StringUtils::isNotBlank).forEach(mailor -> mailService.sendSimpleMail(mailor,
                    "[" + bmDapConfig.getAppName() + "]系统提示：未知异常", throwableDetail));
        }
        result.setMsg(unknownException ? "您好~系统未知异常，请联系管理员！!" : msg);
        result.setCode(ResultBean.FAIL_CODE);
        return result;
    }
}
